﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.IO;
using System.Xml;

namespace EnCor.Wcf.Routing
{
    public delegate void ReProcessMessage(Message requestMessage);

    [ServiceContract(Namespace = "EnCor.Wcf.Routing.IDuplexRouterService", SessionMode = SessionMode.Required, CallbackContract = typeof(IDuplexRouterCallback))]
    public interface IDuplexRouterService
    {
        [OperationContract(IsOneWay = true, Action = "*")]
        void ProcessMessage(Message requestMessage);

    }

    [ServiceContract(Namespace = "EnCor.Wcf.Routing.IDuplexRouterCallback", SessionMode = SessionMode.Allowed)]
    public interface IDuplexRouterCallback
    {
        [OperationContract(IsOneWay = true, Action = "*")]
        void ProcessMessage(Message requestMessage);

    }


    [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple, AddressFilterMode = AddressFilterMode.Any, ValidateMustUnderstand = false)]
    public class DuplexRouterService : IDuplexRouterService, IDisposable
    {

        object m_duplexSessionLock = new object();
        IDuplexRouterService m_duplexSession;
        int invokeServiceHasCode;
        ReProcessMessage reProcess;
        IDuplexRouterCallback callback;
        
        #region IRouterService Members

        public void ProcessMessage(Message requestMessage)
        {
            reProcess = new ReProcessMessage(ProcessMessage);

            MessageBuffer messageBuffer = requestMessage.CreateBufferedCopy(int.MaxValue);
            Message messageCopy = messageBuffer.CreateMessage();
            Message newRequestMessage = messageBuffer.CreateMessage();
            UniqueId messageId = requestMessage.Headers.MessageId;

            Console.WriteLine("ProcessMessage ID: {0}", messageId.ToString());
            Log4netHelper.Log.Info("DuplexRouterService.ProcessMessage(), MessageId = " + messageId);

            lock (this.m_duplexSessionLock)
            {
                if (this.m_duplexSession == null || true) //
                {
                    if(callback == null)
                        callback = OperationContext.Current.GetCallbackChannel<IDuplexRouterCallback>();

                    Binding binding = null;
                    EndpointAddress endpointAddress = null;
                    RegisterService.GetServiceEndpoint(newRequestMessage, out binding, out endpointAddress, out invokeServiceHasCode, messageId);
                    RegisterService.KeepCalledService(messageId, endpointAddress.Uri);

                    NetTcpBinding tcpBinding = binding as NetTcpBinding;
                    tcpBinding.Security.Message.ClientCredentialType = MessageCredentialType.None;
                    tcpBinding.Security.Mode = SecurityMode.None;

                    DuplexChannelFactory<IDuplexRouterService> factory = new DuplexChannelFactory<IDuplexRouterService>(
                        new InstanceContext(null, new DuplexRouterCallback(callback, invokeServiceHasCode, reProcess, messageCopy, messageId)),
                        tcpBinding, 
                        endpointAddress);
                    factory.Endpoint.Behaviors.Add(new MustUnderstandBehavior(false));
                    this.m_duplexSession = factory.CreateChannel();
                }
            }

            Console.WriteLine(string.Format("IDuplexRouterService.ProcessRequest On -{0}",DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")) );
            Console.WriteLine("\t{0}", newRequestMessage.Headers.Action);

            RegisterService.StartServiceInvoke(invokeServiceHasCode);
            this.m_duplexSession.ProcessMessage(newRequestMessage);
        }

        #endregion


        #region IDisposable Members

        public void Dispose()
        {
            if (this.m_duplexSession != null)
            {
                try
                {
                    ICommunicationObject obj = this.m_duplexSession as ICommunicationObject;
                    obj.Close();
                }
                catch { }
            }
        }

        #endregion
    }

    public class DuplexRouterCallback : IDuplexRouterCallback
    {

        private IDuplexRouterCallback m_clientCallback;
        private int m_invokeServiceHascode;
        ReProcessMessage m_reProcess;
        Message m_messageCopy;
        UniqueId m_messageId;

        public DuplexRouterCallback(IDuplexRouterCallback clientCallback, int invokeServiceHascode, ReProcessMessage reProcess, Message messageCopy, UniqueId messageId)
        {
            m_clientCallback = clientCallback;
            m_invokeServiceHascode = invokeServiceHascode;
            m_reProcess = reProcess;
            m_messageCopy = messageCopy;
            m_messageId = messageId;

        }

        #region IDuplexRouterCallback Members

        public void ProcessMessage(Message requestMessage)
        {
            if (requestMessage.IsFault)
            {
                Console.WriteLine("DuplexRouterCallback.ProcessMessage(),  RequestMessage is Fault");
                Log4netHelper.Log.Info("DuplexRouterCallback.ProcessMessage(),  RequestMessage is Fault"); 

                RegisterService.EndServiceInvoke(m_invokeServiceHascode);
                // RegisterService.ServiceInvokeError(m_invokeServiceHascode);
                // find anable service to process message
                if (m_reProcess != null)
                {
                    m_reProcess.Invoke(m_messageCopy);
                }
            }
            else
            {
                Console.WriteLine("DuplexRouterCallback.ProcessMessage(), Seccess");
                Log4netHelper.Log.Info("DuplexRouterCallback.ProcessMessage(), Seccess MessageId=" + requestMessage.Headers.MessageId.ToString()); 

                Console.WriteLine("\t{0}", requestMessage.Headers.Action);
                try
                {
                    RegisterService.RemoveCalledService(m_messageId);
                    this.m_clientCallback.ProcessMessage(requestMessage);
                }
                finally
                {
                    RegisterService.EndServiceInvoke(m_invokeServiceHascode);
                }
            }
        }


        #endregion
    }
}
